# -*- coding: utf-8 -*-
# @Time    : 2024/6/12 17:06
# @Author  : yujiahao
# @File    : 19_lambda_closure.py.py
# @description: Python中的匿名函数和闭包


#  todo 一、匿名函数

"""
Java、Scala 和 Python 的匿名函数（也称为 lambda 表达式）在语法和使用上有所不同。以下是相关的详细知识点：

Java：使用Spark或者Flink的时候用的很多，但是要注意有的jdk不知道返回值类型，要最后return一下返回值的类型，注意一下
-------
在 Java 中，匿名函数是通过 lambda 表达式实现的。它们通常用于简化代码，特别是在处理集合和流操作时。
语法：
    (parameters) -> expression

示例：
    // Lambda 表达式用于实现 Runnable 接口
    Runnable r = () -> System.out.println("Hello, World!");
    new Thread(r).start();

    // Lambda 表达式用于集合操作
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    numbers.forEach(n -> System.out.println(n));

    // Lambda 表达式用于过滤和映射
    List<String> strings = Arrays.asList("a", "b", "c", "d");
    List<String> filtered = strings.stream()
                                   .filter(s -> !s.isEmpty())
                                   .collect(Collectors.toList());

注意：
    - Lambda 表达式可以有零个或多个参数。
    - 如果只有一个参数，可以省略括号。
    - 如果表达式体只有一行，可以省略大括号和 return 关键字。
    - Lambda 表达式可以捕获外部变量（称为闭包），但这些变量必须是 effectively final（即在 lambda 表达式内部不能修改）。

Scala：Scala就是函数式编程，这个很有搞头，有了解的可以去看看，一个函数最后可以省略成很简单的【至简原则】,你甚至可以这么写：val add = _+_
-------
在 Scala 中，匿名函数也称为函数文字（function literals）或 lambda 表达式。它们是非常常见的函数式编程构造。
语法：
    (parameters) => expression

示例：
    // 匿名函数赋值给变量
    val add = (x: Int, y: Int) => x + y
    println(add(2, 3))  // 输出: 5

    // 匿名函数用于集合操作
    val numbers = List(1, 2, 3, 4, 5)
    numbers.foreach(n => println(n))

    // 使用下划线作为参数占位符
    val squares = numbers.map(_ * _)
    println(squares)  // 输出: List(1, 4, 9, 16, 25)

    // 高阶函数使用匿名函数
    def applyOperation(f: (Int, Int) => Int, a: Int, b: Int): Int = f(a, b)
    val result = applyOperation((x, y) => x * y, 3, 4)
    println(result)  // 输出: 12

注意：
    - Scala 的匿名函数可以有多个参数，参数类型可以显式声明或由编译器推断。
    - 可以使用下划线 _ 作为参数的占位符。
    - 支持闭包，可以捕获和使用外部变量。

Python：
-------
在 Python 中，匿名函数通过 lambda 关键字创建。它们通常用于简短的、一次性的操作。
语法:

　　函数名 = lambda 参数:返回值

     1）此函数不是没有名字，他是有名字的，他的名字就叫做lambda

     2）lambda 是定义匿名函数的关键字，相当于函数的def.

     3）lambda 后面直接加形参，形参加多少都可以，只要用逗号隔开就行。

     4）返回值在冒号之后设置，返回值和正常的函数一样,可以是任意数据类型。(但是想要返回多个元素要以容器的形式返回)

     5）匿名函数不管多复杂.只能写一行.且逻辑结束后直接返回数据

示例：
    # 匿名函数赋值给变量
    add = lambda x, y: x + y
    print(add(2, 3))  # 输出: 5

    # 匿名函数用于列表操作
    numbers = [1, 2, 3, 4, 5]
    squares = list(map(lambda n: n ** 2, numbers))
    print(squares)  # 输出: [1, 4, 9, 16, 25]

    # Lambda 表达式用于排序
    pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
    pairs.sort(key=lambda pair: pair[1])
    print(pairs)  # 输出: [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

    # Lambda 表达式用于过滤
    even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
    print(even_numbers)  # 输出: [2, 4]

注意：
    - Lambda 表达式只能包含一个表达式，不允许包含复杂的语句。
    - 它们通常用于需要简单函数的场合，如排序、过滤和映射操作。
    - 支持闭包，可以捕获和使用外部变量。

总结：
-------
- Java 使用 (parameters) -> expression 语法创建 lambda 表达式，主要用于接口的实现和集合操作。
- Scala 使用 (parameters) => expression 语法创建匿名函数，广泛用于函数式编程。
- Python 使用 lambda parameters: expression 语法创建匿名函数，适用于简短的操作。

每种语言的匿名函数都有其独特的语法和使用场景，但它们的核心思想是相同的：提供一种简洁的方式来定义一次性使用的小函数。
"""


# 看个例子
'''现在有一个需求：你们写一个函数，此函数接收两个int参数，返回和值。'''

# 一般的函数
def add(x,y):
    return x + y
print(add(1,2))



# 使用匿名函数这样写
add_lambda = lambda x,y: x + y
print(add_lambda(1,2))


# 所有类型的形参都可以加，但是一般使用匿名函数只是加位置参数，其他的用不到。
func = lambda a,b,*args,sex= 'alex',c,**kwargs: kwargs
print(func(3, 4,c=666,name='alex'))


#练习
'''写匿名函数：接收一个可切片的数据，返回索引为0与2的对应的元素（元组形式）'''
fun = lambda x:(x[0],x[2])
print(fun('wazryzd'))


print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 二、闭包


"""
闭包的定义：在函数嵌套的前提下，内部函数使用了外部函数的变量，并且外部函数返回了内部函数，我们把这个使用外部函数变量的内部函数称为闭包
       
   1、闭包是嵌套在函数中的函数。
   2、闭包必须是内层函数对外层函数的变量（非全局变量）的引用。

闭包的作用：保存局部信息不被销毁，保证数据的安全性。

闭包的应用：
    
    1、回调函数：在异步编程或事件驱动编程中，闭包可以用来保存回调函数的上下文。
    2、工厂函数：闭包可以用来创建带有某些固定参数的函数。
    3、数据封装：闭包可以用来创建带有私有数据的函数，从而实现数据封装。
    4、装饰器：在 Python 中，闭包是实现装饰器的基础。

必报的构成条件：

    - 在函数嵌套（函数里面在定义函数）的前提下
    - 内部函数使用了外部函数的变量（还包括外部函数的参数）
    - 外部函数返回了内部函数

闭包书写步骤：

    - 定义外部函数
    - 定义外部函数，在内部函数中使用外部函数的变量
    - 外部函数返回内部函数的地址


一、Java中的闭包 Java没有闭包
Java 8 引入了 Lambda 表达式和函数式接口，这为闭包提供了支持。虽然 Java 的闭包不像 Python 和 Scala 那样灵活，
但通过 Lambda 表达式和匿名内部类，可以实现类似闭包的功能。

示例：
import java.util.function.Function;

public class ClosureExample {
    public static void main(String[] args) {
        int num = 10;
        Function<Integer, Integer> adder = (x) -> x + num;
        
        // 即使在此作用域外调用，adder 仍然记住了 num 的值
        System.out.println(adder.apply(5));  // 输出: 15
    }
}

在这个例子中，Lambda 表达式 (x) -> x + num 创建了一个闭包，捕获了外部变量 num。

注意事项：
1. Java 中的闭包只能捕获 final 或 effectively final（即实际上没有改变的）变量。因此，如果你尝试在闭包中修改捕获的变量，会导致编译错误。
2. 由于 Java 的类型系统和语法限制，闭包的使用不如 Python 和 Scala 那样灵活。

二、Scala中的闭包，Scala中闭包是核心概念，结合Scala理解一下，将函数对象作为参数进行值传递，函数都是一等公民
Scala 是一门支持函数式编程的语言，闭包是其核心特性之一。Scala 的闭包可以捕获并记住其创建时的环境变量。

示例：
object ClosureExample {
  def main(args: Array[String]): Unit = {
    var num = 10
    val adder = (x: Int) => x + num
    
    // 修改外部变量 num 的值
    num = 20
    
    // 即使 num 的值改变，adder 仍然记住了创建时 num 的值
    println(adder(5))  // 输出: 25
  }
}

在这个例子中，匿名函数 (x: Int) => x + num 创建了一个闭包，捕获了外部变量 num。

注意事项：
1. Scala 的闭包可以捕获可变变量（var），这与 Java 不同。因此，在使用闭包时要注意变量的可变性可能带来的副作用。
2. 闭包在并发环境下使用时需要特别小心，捕获的可变变量可能会引发线程安全问题。

三、Python中的闭包
Python 的闭包非常灵活，可以轻松捕获和访问其创建时的环境变量。Python 中的闭包通常通过嵌套函数来实现。

示例：
def make_adder(num):
    def adder(x):
        return x + num
    return adder

adder = make_adder(10)
print(adder(5))  # 输出: 15

在这个例子中，adder 函数捕获了 make_adder 函数中的 num 变量，形成了闭包。

注意事项：
1. 在 Python 中，闭包捕获的变量是通过引用而不是值，因此如果外部变量在闭包创建后被修改，闭包中访问到的将是修改后的值。
2. 为了避免上述问题，可以使用默认参数值或 functools.partial 来创建闭包，确保捕获的变量值不会改变。
3.闭包可能导致的循环引用问题，需要合理管理。
4.理解闭包中变量的作用域和生命周期很重要，避免意外的行为。

总结：
1. Java 通过 Lambda 表达式和匿名内部类实现闭包，但其功能和灵活性不如 Scala 和 Python。闭包只能捕获 final 或 effectively final 变量。
2. Scala 是函数式编程语言，闭包是其核心特性之一，能够捕获并记住创建时的环境变量。需要注意捕获可变变量带来的副作用。
3. Python 的闭包非常灵活，通过嵌套函数可以轻松实现，能够捕获和访问创建时的环境变量。需要注意捕获变量是通过引用的特性。
"""



# 慢慢理解一下


'''
由于闭包这个概念比较难以理解，尤其是初学者来说，相对难以掌握，所以我们通过示例去理解学习闭包。
给大家提个需求，然后用函数去实现：完成一个计算不断增加的系列值的平均值的需求。
例如：整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢？就是从这个商品一出现开始，每天记录当天价格，然后计算他的平均值：平均值要考虑直至目前为止所有的价格。

比如大众推出了一款新车：小白轿车。
第一天价格为：100000元，平均收盘价：100000元
第二天价格为：110000元，平均收盘价：（100000 + 110000）/2 元
第三天价格为：120000元，平均收盘价：（100000 + 110000 + 120000）/3 元
........

'''


# 一般的可以这么写，但是不安全

# 用来存每天的价格，然后列表长度就是代表天数
series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)

print(make_averager(100000))
print(make_averager(110000))
series.append(666)  # 如果对数据进行相应改变，那么平均收盘价就会出现很大的问题。
print(make_averager(120000))


# 用闭包的思想改一下这个代码。
'''
闭包书写步骤：

    - 定义外部函数
    - 定义外部函数，在内部函数中使用外部函数的变量
    - 外部函数返回内部函数的地址
'''
def make_averager():

    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

    return averager

avg = make_averager()

print(avg(100000))
print(avg(110000))
print(avg(120000))



'''
案例：根据配置信息使用闭包实现不同人的对话信息，例如对话：
张三：到北京了吗？

李四：已经到了，放心吧。

实现步骤说明
定义外部函数接受不同的配置信息参数，参数是人名
定义内部函数接受对话信息参数
在内部函数里面把配置信息和对话信息进行拼接输出
'''
def outer(name):
    # 定义内部函数，参数是 说话的信息
    print(f'{name}:到北京了吗?')

    def inner(name1):
        # 内部函数中，将name和info进行拼接输出
        print(f'{name1}：已经到了，放心吧。')

    # 外部函数返回内部函数的地址
    return inner



# 练习一下，下面的三个例子是不是闭包 ，分析在下面的注释中

# 例一：
def wrapper():
    a = 1
    def inner():
        print(a)
    return inner
ret = wrapper()

# 例二：
a = 2
def wrapper():
    def inner():
        print(a)
    return inner
ret = wrapper()


# 例三：

def wrapper(a,b):
    def inner():
        print(a)
        print(b)
    return inner
a = 2
b = 3
ret = wrapper(a,b)


# 案例解析

"""
在 Python 中，闭包（closure）是指一个函数对象，即使在其词法作用域外被调用时，仍然能够记住并访问其定义时的环境变量。换句话说，闭包可以捕获和携带定义时的自由变量（即非局部变量）。

让我们逐一分析以下三个例子，以确定哪些是闭包。

例一：

def wrapper():
    a = 1
    def inner():
        print(a)
    return inner

ret = wrapper()

在这个例子中，inner 函数引用了 wrapper 函数中的局部变量 a。当 wrapper 函数返回 inner 函数时，inner 函数仍然能够访问 a，即使 wrapper 函数已经执行完毕。因此，这是一个闭包。

例二：

a = 2
def wrapper():
    def inner():
        print(a)
    return inner

ret = wrapper()

在这个例子中，inner 函数引用了全局变量 a。虽然 inner 函数访问了 a，但 a 并不是 wrapper 函数中的局部变量，而是全局变量。因此，这不是一个闭包。

例三：

def wrapper(a, b):
    def inner():
        print(a)
        print(b)
    return inner

a = 2
b = 3
ret = wrapper(a, b)

在这个例子中，inner 函数引用了 wrapper 函数的参数 a 和 b。当 wrapper 函数返回 inner 函数时，inner 函数仍然能够访问 a 和 b，即使 wrapper 函数已经执行完毕。因此，这是一个闭包。

总结：

- 例一 是闭包，因为 inner 函数引用了 wrapper 函数中的局部变量 a。
- 例二 不是闭包，因为 inner 函数引用了全局变量 a，而不是 wrapper 函数中的局部变量。
- 例三 是闭包，因为 inner 函数引用了 wrapper 函数的参数 a 和 b。

因此，例一和例三是闭包，而例二不是闭包。
"""


# 以上三个例子，最难判断的是第三个，其实第三个也是闭包，如果我们每次去研究代码判断其是不是闭包，有一些不科学，或者过于麻烦了，
# 那么有一些函数的属性是可以获取到此函数是否拥有自由变量的，如果此函数拥有自由变量，那么就可以侧面证明其是否是闭包函数了（了解）：

def make_averager():

    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

    return averager
avg = make_averager()
# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)

# 当然还有一些参数，仅供了解：

# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)
# 函数名.__code__.co_varnames 查看函数的局部变量
print(avg.__code__.co_varnames)  # ('new_value', 'total')
# 函数名.__closure__ 获取具体的自由变量对象，也就是cell对象。
# (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,)
# cell_contents 自由变量具体的值
print(avg.__closure__[0].cell_contents)  # []

# 创建闭包实例
if __name__ == '__main__':
    func = outer('张三')
    func('李四')

#     或者这么调用
outer('\nyujiahao')('wangzuxian')
